package ${packages.fiscoConfig};

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.fisco.bcos.sdk.BcosSDK;
import org.fisco.bcos.sdk.client.Client;
import org.fisco.bcos.sdk.config.ConfigOption;
import org.fisco.bcos.sdk.config.model.ConfigProperty;
import org.fisco.bcos.sdk.crypto.CryptoSuite;
import org.fisco.bcos.sdk.crypto.keypair.CryptoKeyPair;
import org.fisco.bcos.sdk.transaction.manager.AssembleTransactionProcessor;
import org.fisco.bcos.sdk.transaction.manager.TransactionProcessorFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * FISCO BCOS 客户端与交易处理器配置。
 */
@Configuration
@ConditionalOnProperty(prefix = "feature.blockchain", name = "enabled", havingValue = "true", matchIfMissing = false)
@Slf4j
@RequiredArgsConstructor
public class BlockchainConfig {

    private final FiscoProperties properties;

    @Bean
    public BcosSDK bcosSDK() throws Exception {
        ConfigProperty configProperty = new ConfigProperty();

        Map<String, Object> cryptoMaterial = new HashMap<>();
        cryptoMaterial.put("certPath", properties.getCrypto().getCertPath());
        cryptoMaterial.put("useSMCrypto", String.valueOf(properties.getCrypto().isUseSmCrypto()));
        configProperty.setCryptoMaterial(cryptoMaterial);

        Map<String, Object> network = new HashMap<>();
        List<String> peers = properties.getNetwork().getPeers();
        if (peers == null || peers.isEmpty()) {
            log.warn("未配置任何节点地址，BcosSDK 将无法连接区块链网络");
        } else {
            network.put("peers", peers);
        }
        network.put("defaultGroup", properties.getNetwork().getGroupId());
        configProperty.setNetwork(network);

        ConfigOption option = new ConfigOption(configProperty);
        log.info("初始化 BcosSDK，群组: {}，节点: {}", properties.getNetwork().getGroupId(), peers);
        return new BcosSDK(option);
    }

    @Bean
    public Client client(BcosSDK sdk) {
        return sdk.getClient(properties.getNetwork().getGroupId());
    }

    @Bean
    public CryptoSuite cryptoSuite(Client client) {
        return client.getCryptoSuite();
    }

    @Bean
    public CryptoKeyPair cryptoKeyPair(CryptoSuite cryptoSuite) {
        String hexPrivateKey = properties.getAccount().getHexPrivateKey();
        if (hexPrivateKey != null && !hexPrivateKey.isBlank()) {
            return cryptoSuite.getKeyPairFactory().createKeyPair(hexPrivateKey);
        }
        log.warn("未配置默认私钥，将使用随机账号");
        return cryptoSuite.getKeyPairFactory().generateKeyPair();
    }

    @Bean
    public AssembleTransactionProcessor transactionProcessor(Client client, CryptoKeyPair keyPair) throws Exception {
        String abiDir = resolveClasspathDirectory(properties.getContract().getAbiPath());
        String binDir = resolveClasspathDirectory(properties.getContract().getBinPath());
        log.info("加载合约 ABI 目录: {}", abiDir);
        log.info("加载合约 BIN 目录: {}", binDir);
        return TransactionProcessorFactory.createAssembleTransactionProcessor(client, keyPair, abiDir, binDir);
    }

    public String getContractName() {
        return properties.getContract().getName();
    }

    public String getContractAddress() {
        return properties.getContract().getAddress();
    }

    private String resolveClasspathDirectory(String location) throws IOException {
        ClassPathResource resource = location.startsWith("classpath:")
                ? new ClassPathResource(location.substring("classpath:".length()))
                : new ClassPathResource(location);

        if (!resource.exists()) {
            File dir = new File(location);
            if (dir.exists()) {
                return dir.getAbsolutePath();
            }
            throw new IOException("未找到目录: " + location);
        }
        return resource.getFile().getAbsolutePath();
    }
}
